//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.Activities
{
    #region Using Directives

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.ComponentModel;
    using System.Workflow.ComponentModel;
    using System.Collections;
    using System.Collections.Specialized;
    using System.Workflow.ComponentModel.Design;
    using System.ComponentModel.Design;
    using Microsoft.Research.ScientificWorkflow.NetCDF.CSharpAPI;
    using System.Workflow.ComponentModel.Compiler;

    using Microsoft.Research.ScientificWorkflow;
    using Microsoft.Research.ScientificWorkflow.Helpers;
    
    using WorkflowData;

    #endregion

    #region HyperCubeCasrTransformerActivity Class

    [Name("Variable Min Max Transform")]
    [Description("Transform for finding the min and max values for a variable")]
    [WorkflowCategory("NetCdf")]
    public class MinMaxTransformerActivity : Activity
    {
        
        #region Private Fields

        private const string HyperCuberPropertyName = "GenerateHyperCubeActivity";
        //Used to get the variable attribute of this name.
        private const string MissingValue = "missing_value"; 

        #endregion

        #region Dependency Properties

        public static DependencyProperty VariableNameProperty = DependencyProperty.Register("VariableName",
            typeof(string), typeof(MinMaxTransformerActivity));

        /// <summary>
        /// Variable Input property
        /// </summary>
        [RequiredInputParam]
        [Name("Variable Name")]
        [Description("Variable Name for which the min/max value is to be found.")]
        public string VariableName
        {
            get
            {
                return ((string)(base.GetValue(MinMaxTransformerActivity.VariableNameProperty)));
            }
            set
            {
                base.SetValue(MinMaxTransformerActivity.VariableNameProperty, value);
            }
        }

        public static DependencyProperty InputCubeProperty =
            DependencyProperty.Register("InputCube", typeof(HyperCube), typeof(MinMaxTransformerActivity));

        [RequiredInputParam]
        [Name("In Hyper Cube")]
        [Description("Hyper Cube for finding Min and Max")]
        public HyperCube InputCube
        {
            get
            {
                return ((HyperCube)(base.GetValue(MinMaxTransformerActivity.InputCubeProperty)));
            }
            set
            {
                base.SetValue(MinMaxTransformerActivity.InputCubeProperty, value);
            }
        }

        public static DependencyProperty MinProperty = DependencyProperty.Register("Min", 
            typeof(object), typeof(MinMaxTransformerActivity));

        /// <summary>
        /// Min Output property
        /// </summary>
        [OutputParam]
        [Name("Min Value")]
        [Description("Lowest value for the specified variable.")]
        public object Min
        {
            get
            {
                return ((object)(base.GetValue(MinMaxTransformerActivity.MinProperty)));
            }
            set
            {
                base.SetValue(MinMaxTransformerActivity.MinProperty, value);
            }
        }

        public static DependencyProperty MaxProperty = DependencyProperty.Register("Max", 
            typeof(object), typeof(MinMaxTransformerActivity));

        /// <summary>
        /// Max Output property
        /// </summary>
        [OutputParam]
        [Name("Max Value")]
        [Description("Highest value for the specified variable.")]
        public object Max
        {
            get
            {
                return ((object)(base.GetValue(MinMaxTransformerActivity.MaxProperty)));
            }
            set
            {
                base.SetValue(MinMaxTransformerActivity.MaxProperty, value);
            }
        }

        #endregion
        
        #region Overridden Methods

        /// <summary>
        /// Extract min and max value
        /// </summary>
        /// <param name="executionContext"></param>
        /// <returns></returns>
        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
        {
            HyperCube InputHyperCube = this.InputCube;

            int xLen = InputHyperCube.GetAxisLength(HyperCube.Axis.X);
            int yLen = InputHyperCube.GetAxisLength(HyperCube.Axis.Y);
            int zLen = InputHyperCube.GetAxisLength(HyperCube.Axis.Z);
            int tLen = InputHyperCube.GetAxisLength(HyperCube.Axis.T);

            int i = InputHyperCube.Schema.LookupColumnIndex(this.VariableName);
            if (-1 == i)
                throw new ArgumentException("MinMaxTransform:Invalid Variable Name");

            //first get the missing value of the variable
            object missingValue = InputHyperCube.Schema.GetAttribute(i, ScalerTransform.MissingValue);
            string varName = InputHyperCube.Schema.GetColumnName(i);
            //NcVar var = netCDFFile.NcMetaData.GetVariable(varName);

            //NcAtt att = var.GetAtt(MissingValue);
            //if (att != null)
            //{
            //    missingValue = (object)att.Value.DataValue;
            //}
            
            for (int x = 0; x < xLen; x++)
            {
                for (int y = 0; y < yLen; y++)
                {
                    for (int z = 0; z < zLen; z++)
                    {
                        for (int t = 0; t < tLen; t++)
                        {
                            object val = InputHyperCube[x, y, z, t][i];
                            this.SetMinMaxValues(val, missingValue);
                        }
                    }
                }
            }

            //Assing the values to the Min and Max output properties
            this.Min = this.minValue;
            this.Max = this.maxValue;
            
            return ActivityExecutionStatus.Closed;
        }

        #endregion

        #region Private Methods

        #region Set Min and Max values

        /// <summary>
        /// Storage for min value
        /// </summary>
        object minValue = null;
        /// <summary>
        /// Storage for max value
        /// </summary>
        object maxValue = null;

        /// <summary>
        /// Set the min and max values comparing with the parameter value
        /// </summary>
        /// <param name="currentValue"></param>
        private void SetMinMaxValues(object currentValue, object missingValue)
        {
            //Ignore null value while comparing 
            if (null == currentValue)
                return;

            if (null == minValue)
            {
                //Assign first ever value
                minValue = currentValue;
                maxValue = currentValue;
            }
            else
            {
                //Set min and max values based on its data type
                if (currentValue is int)
                {
                    int temp = (int)currentValue;
                    if (missingValue != null)
                    {
                        // Here temp should be compared with "Missing Value" of the variable if present
                        int[] missing = (int[])missingValue;
                        if (temp == missing[0])
                            return;  //Ignore comparison
                    }
                    minValue = temp < (int)minValue ? temp : minValue;
                    maxValue = temp > (int)maxValue ? temp : maxValue;
                }
                else if (currentValue is float)
                {
                    float temp = (float)currentValue;
                    if (missingValue != null)
                    {
                        // Here temp should be compared with "Missing Value" of the variable if present
                        float[] missing = (float[])missingValue;
                        if (temp == missing[0])
                            return;  //Ignore comparison
                    }
                    minValue = temp < (float)minValue ? temp : minValue;
                    maxValue = temp > (float)maxValue ? temp : maxValue;
                }
                else if (currentValue is short)
                {
                    short temp = (short)currentValue;
                    if (missingValue != null)
                    {
                        // Here temp should be compared with "Missing Value" of the variable if present
                        short[] missing = (short[])missingValue;
                        if (temp == missing[0])
                            return;  //Ignore comparison
                    }
                    minValue = temp < (short)minValue ? temp : minValue;
                    maxValue = temp > (short)maxValue ? temp : maxValue;
                }
                else if (currentValue is double)
                {
                    double temp = (double)currentValue;
                    if (missingValue != null)
                    {
                        // Here temp should be compared with "Missing Value" of the variable if present
                        double[] missing = (double[])missingValue;
                        if (temp == missing[0])
                            return;  //Ignore comparison
                    }
                    minValue = temp < (double)minValue ? temp : minValue;
                    maxValue = temp > (double)maxValue ? temp : maxValue;
                }
            }
        }

        #endregion

        #endregion

    }

    #endregion
}
